﻿using System;
using System.Collections.Generic;

using System.Text;
using System.IO.Ports;
using System.Threading;
using BGM.Sensors.MeasuringDevice;
using System.IO;

namespace BayerContour
{
    class BayerCommController
    {   /// <summary>
        /// Control chars used in messages with device
        /// </summary>
        private enum ControlChar
        {
            STX = 0x02,
            ETX = 0x03,
            EOT = 0x04,
            ENQ = 0x05,
            ACK = 0x06,
            TAB = 0x09,
            CR = 0x0d,
            LF = 0x0a,
            NAK = 0x15,
            ETB = 0x17,
            NOT_DEFINED = 0
        }
        public struct Frame
        {
            public int number;
            public string data;
            public bool endFrame;
        };
        private int frameNumber = 0;
        private ManualResetEvent waitForDevice = new ManualResetEvent(false);
        private ManualResetEvent waitForUpload = new ManualResetEvent(false);
        private IPort port;
        enum States { Init, Receive, ReceiveingMessage, Abort, CheckSumValidate, Saving, Unknown };
        States state = States.Unknown;
        string dataBuffer = string.Empty;
        List<Frame> buffer = new List<Frame>();
        string recievedData = string.Empty;
        
        public BayerCommController()
        {
        }
       
        /// <summary>
        /// Convert Control enum to string
        /// </summary>
        /// <param name="c">ControlChar enum</param>
        /// <returns>Control char as a string</returns>
        private static string CC2S(ControlChar c)
        {
            return ((char)c).ToString();
        }
        #region Debug methods
        public static string DebugEncodeMessage(string tmp)
        {
            string message = tmp;
            foreach (var item in Enum.GetNames(typeof(ControlChar)))
            {
                if (message.Contains(item))
                {
                    var ddd = Enum.GetValues(typeof(ControlChar));
                    var t = (ControlChar)Enum.Parse(typeof(ControlChar), item, true);
                    var t2 = t.GetType();

                    message = message.Replace("<" + item + ">", CC2S(t));
                }
            }
            return message;
        }
        public static string DebugDecodeMessage(string tmp)
        {
            string t = string.Empty;
            foreach (char item in tmp.ToCharArray())
            {
                try
                {
                    string name = Enum.GetName(typeof(ControlChar), (byte)item);
                    if (name!=null && name.Length > 0)
                        t += "<" + name + ">";
                    else
                        t += item;
                }
                catch (Exception)
                {

                    t += item;
                }

            }
            return t;
        }
        #endregion

        
        //void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
        //{
        //    lock (this)
        //        recievedData += port.ReadExisting();
        //    waitForDevice.Set();

        //    // DoAction(port.ReadExisting());

        //}
        /// <summary>
        /// Event raised when a message is recieved
        /// </summary>
        public event MessageRecievedEventhandler MessageRecieved;

        public List<Frame> Upload(string serialPortName)
        {
            this.port = new CustomSerialPort(serialPortName,9600, Parity.None,8, StopBits.One);// port;
            this.port.DtrEnable = true;
            this.port.RtsEnable = true;
            return Upload();
        }
        public List<Frame> Upload(Stream stream)
        {
            this.port = new StreamPort(stream);
            return Upload();
        }

        private List<Frame> Upload()
        {
            state = States.Init;
            Thread t = new Thread(new ThreadStart(HandleData));
            t.IsBackground = true;
            t.Start();
            this.port.DataReceived += new EventHandler(port_DataReceived); 
            this.port.Open();
            waitForUpload.WaitOne(30 * 1000, true);
            return buffer;
        }

        void port_DataReceived(object sender, EventArgs e)
        {
            lock (this)
                recievedData += port.ReadExisting();
            waitForDevice.Set();
        }

        protected void OnMessageRecieved(MessageRecievedEventArgs e)
        {
            MessageRecievedEventhandler handler = MessageRecieved;
            if (handler != null)
                handler(this, e);
        }
        public void Close()
        {
            try
            {
                if(port!=null && port.IsOpen)
                port.Close();
            }
            catch (Exception)
            {

                throw;
            }
        }
        private void HandleData()
        {

            while (true)
            {

                waitForDevice.WaitOne();
                lock (this)
                {
                    DoAction(recievedData);
                    recievedData = string.Empty;
                }
                waitForDevice.Reset();



            }

        }
        private void DoAction(string data)
        {

            //var hhh = DebugDecodeMessage(data);
            //var bu = DebugDecodeMessage(dataBuffer);
            switch (state)
            {
                case States.Init:
                    if (data.Contains(CC2S(ControlChar.ENQ)))
                    {
                        SendControlChar(ControlChar.ACK);
                        ChangeState(States.Receive);
                        buffer.Clear();
                        frameNumber = 0;
                    }
                    break;
                case States.Receive:
                    frameNumber = (frameNumber + 1) % 8;
                    if (data.Contains(CC2S(ControlChar.STX)))
                    {
                        // dataBuffer += data;
                        ChangeState(States.ReceiveingMessage);
                        DoAction(data);
                    }
                    break;
                case States.ReceiveingMessage:
                    dataBuffer += data;
              //      var message = DebugDecodeMessage(dataBuffer);
                    if (data.Contains(CC2S(ControlChar.LF)))
                    {
                        if (ValidateCheckSum(dataBuffer))
                        {
                            var currentFrame = ExstractFrame(dataBuffer);
                            if (frameNumber == currentFrame.number)
                            {
                                SendControlChar(ControlChar.ACK);
                                buffer.Add(currentFrame);
                                OnMessageRecieved(new MessageRecievedEventArgs(currentFrame.number, DebugDecodeMessage(dataBuffer)));
                                if (currentFrame.endFrame && IsMessageTerminator(dataBuffer))
                                {
                                    ChangeState(States.Abort);
                                    SignalUploadFinish();
                                }
                                else
                                {
                                    dataBuffer = string.Empty;
                                    ChangeState(States.Receive);
                                }
                            }
                        }
                        else
                            SendControlChar(ControlChar.NAK);
                    }
                    break;
                case States.Abort:
                    SendControlChar(ControlChar.NAK);
                    break;
            }
            //if (data.Contains(CC2S(ControlChar.EOT )))
            //{
            //    ChangeState(States.Abort);
            //}
        }

        private void SignalUploadFinish()
        {
            waitForUpload.Set();
        }
        private bool IsMessageTerminator(string msg)
        {
            if (msg.Contains("L|1|N"))
                return true;
            else
                return false;
        }
        /// <summary>
        /// Send control char to device
        /// </summary>
        /// <param name="c">Enum controlchar to send</param>
        private void SendControlChar(ControlChar c)
        {
            //Debug.WriteLine("Sending control Char: " + c.ToString());
            port.Write(((char)c).ToString());
        }
        private bool ValidateCheckSum(string message)
        {
            message = message.Trim();
            string tmp = string.Empty;
            if (message.Length > 1)
                tmp = message.Remove(0, 1);
            string data = string.Empty;
            uint t = 0;
            foreach (char var in tmp)
            {
                t += (uint)var;

                if (var == (char)ControlChar.ETX || var == (char)ControlChar.ETB)
                    break;
                data += var;
            }
            byte b = (byte)(t & 0x00FF); //remoce most significant byte
            uint d = t & 0xFF00; //remoce most significant byte
            if (message.Length >= data.Length + 1 + 2 && message.Substring(message.LastIndexOf(data) + data.Length + 1, 2) == b.ToString("X").PadLeft(2, '0'))
                return true;
            else
                return false;
        }

        private void ChangeState(States newState)
        {
            //  lock (state)
            state = newState;
            waitForDevice.Set();
        }

       
        public Frame ExstractFrame(string message)
        {
            Frame currentFarme = new Frame();
            if (message.StartsWith(CC2S(ControlChar.STX)))
            {
                currentFarme.number = int.Parse(message[1].ToString());
                int etbPos = message.IndexOf(CC2S(ControlChar.CR) + CC2S(ControlChar.ETB));
                int etxPos = message.IndexOf(CC2S(ControlChar.CR) + CC2S(ControlChar.ETX));
                currentFarme.endFrame = (etbPos == -1 ? true : false);
                if (currentFarme.endFrame)
                    currentFarme.data = message.Substring(2, message.Length - (message.Length - (etxPos) + 2));
                else
                    currentFarme.data = message.Substring(2, message.Length - (message.Length - (etbPos) + 2));
            }
            return currentFarme;
        }
        //private void DoAction(char d)
        //{
        //    string data = d.ToString();
        //    var hhh = Dec(data);
        //    var bu = Dec(dataBuffer);
        //    switch (state)
        //    {
        //        case States.Init:
        //            if (data.Contains(CC2S(ControlChar.ENQ)))
        //            {
        //                port.WriteLine(CC2S(ControlChar.ACK));
        //                ChangeState(States.Receive);
        //            }
        //            break;
        //        case States.Receive:
        //            if (data.Contains(CC2S(ControlChar.STX)))
        //            {

        //                dataBuffer += data;
        //                ChangeState(States.Receiveing);
        //            }
        //            break;
        //        case States.Receiveing:
        //            dataBuffer += data;
        //            if (data.Contains(CC2S(ControlChar.LF)))
        //            {
        //                ChangeState(States.CheckSumValidate);
        //                DoAction(' ');
        //            }
        //            break;
        //        case States.Abort:
        //            port.WriteLine(CC2S(ControlChar.NAK));
        //            break;
        //        case States.CheckSumValidate:
        //            //buffer.Add(data);

        //            port.WriteLine(CC2S(ControlChar.ACK));
        //            if (data.Contains("L|1|N"))
        //            {
        //                data = string.Empty;
        //                ChangeState(States.Saving);
        //            }
        //            else
        //            {
        //                data = string.Empty;
        //                ChangeState(States.Receive);
        //            }
        //            break;
        //        case States.Saving:
        //            break;
        //        case States.Unknown:
        //            break;
        //        default:
        //            break;
        //    }
        //    //if (data.Contains(CC2S(ControlChar.EOT )))
        //    //{
        //    //    ChangeState(States.Abort);
        //    //}
        //}
    }
}
